package com.googlecode.horncomb.web.spring.tags;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.servlet.jsp.JspException;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.servlet.support.BindStatus;
import org.springframework.web.servlet.support.RequestContext;
import org.springframework.web.servlet.tags.form.AbstractHtmlInputElementTag;
import org.springframework.web.servlet.tags.form.AbstractHtmlElementTag;
import org.springframework.web.servlet.tags.form.TagWriter;

import com.googlecode.horncomb.web.util.TagUtils;

public abstract class AbstractCheckableInputGroupTag extends
		AbstractHtmlInputElementTag {
	private static final long serialVersionUID = 1079867036577538960L;
	public final static String LAYOUT_VERTICAL = "VERTICAL";
	public final static String LAYOUT_HORIZONTAL = "HORIZONTAL";
	public final static String LAYOUT_CUSTOMLABEL = "CUSTOMLABEL";
	public final static String LAYOUT_CUSTOMALL = "CUSTOMALL";
	public final static Set<String> LAYOUT_SET = new HashSet<String>();
	static {
		LAYOUT_SET.add(LAYOUT_VERTICAL);
		LAYOUT_SET.add(LAYOUT_HORIZONTAL);
		LAYOUT_SET.add(LAYOUT_CUSTOMLABEL);
		LAYOUT_SET.add(LAYOUT_CUSTOMALL);
	}
	private Object items;

	/**
	 * Set the items of the '<code>items</code>' attribute. May be a runtime
	 * expression.
	 */
	public void setItems(Object items) {
		Assert.notNull(items, "'items' must not be null");
		this.items = items;
	}

	/**
	 * Get the items of the '<code>items</code>' attribute. May be a runtime
	 * expression.
	 */
	protected Object getItems() {
		return this.items;
	}

	/** 值属性名 */
	private String itemValue;
	/** 文本标签属性名 */
	private String itemLabel;
	/** 变量名 */
	private String varStatus;
	private String varValue;
	private String varLabel = "label";
	/** 布局类型 */
	private String layout = LAYOUT_VERTICAL;
	private String evalLastBody = "false";

	protected Iterator<Object> itemIterator;
	protected int count = 0;
	/**
	 * The {@link TagWriter} instance that the output is being written.
	 * <p>
	 * Only used in conjunction with nested {@link OptionTag OptionTags}.
	 */
	protected TagWriter tagWriter;

	@Override
	public int doAfterBody() throws JspException {
		return this.doWriteTagContent(tagWriter, false);
	}

	@Override
	public int doEndTag() throws JspException {
		// 用于在不写tag body的情况下(<form:checkbox />)，迭代剩余的项
		if (this.itemIterator != null && this.itemIterator.hasNext()) {
			this.writeTagBody(this.tagWriter);
		}
		return EVAL_PAGE;
	}

	/**
	 * @param tagWriter
	 * @param
	 * @return
	 * @throws JspException
	 */
	protected int doWriteTagContent(TagWriter tagWriter, boolean startTag)
			throws JspException {
		if (this.itemIterator != null && this.itemIterator.hasNext()) {
			this.writeTagBody(tagWriter);
			if (this.itemIterator.hasNext()
					|| LAYOUT_CUSTOMALL.equals(this.getLayout())
					|| LAYOUT_CUSTOMLABEL.equals(this.getLayout())
					|| this.getEvalLastBody()) {
				return startTag ? EVAL_BODY_INCLUDE : EVAL_BODY_AGAIN;
			}
		}
		return SKIP_BODY;
	}

	/**
	 * 输出标签内容
	 * @param tagWriter
	 * @return
	 * @throws JspException
	 */
	@SuppressWarnings("unchecked")
	protected void writeTagBody(TagWriter tagWriter) throws JspException {
		if (this.getItems() instanceof Collection) {
			if (this.itemIterator.hasNext()) {
				Object itemValue = this.itemIterator.next();
				BeanWrapperImpl itemValueBeanWrapper = new BeanWrapperImpl(
						itemValue);
				++this.count;
				this.writeItemContent(itemValueBeanWrapper,
						itemValueBeanWrapper);
			}
		} else if (this.getItems() instanceof Map) {
			Map mapValue = (Map) this.getItems();
			if (this.itemIterator.hasNext()) {
				Object item = this.itemIterator.next();
				++this.count;
				BeanWrapperImpl itemValueBeanWrapper = null;
				BeanWrapperImpl itemLabelBeanWrapper = null;
				if (StringUtils.hasText(this.getItemValue())) {
					// 有属性名称，则用map的值作为itemValue
					itemValueBeanWrapper = new BeanWrapperImpl(mapValue
							.get(item));
					itemLabelBeanWrapper = itemValueBeanWrapper;
				} else {
					// 没有属性名称，则用map的key作为itemValue
					itemValueBeanWrapper = new BeanWrapperImpl(item);
					itemLabelBeanWrapper = new BeanWrapperImpl(mapValue
							.get(item));
				}
				this.writeItemContent(itemValueBeanWrapper,
						itemLabelBeanWrapper);
			}
		}
	}

	/**
	 * 输出一项checkbox，包括lable
	 * @param itemValueBeanWrapper
	 * @param itemLabelBeanWrapper
	 * @return
	 * @throws JspException
	 */
	private void writeItemContent(BeanWrapper itemValueBeanWrapper,
			BeanWrapper itemLabelBeanWrapper) throws JspException {
		// items
		Object resolvedItemValue = this.resolveItemValue(itemValueBeanWrapper);

		// label
		String resolvedItemLabel = this.resolveItmeLabel(itemLabelBeanWrapper);

		if (StringUtils.hasText(this.getVarStatus())) {
			Element itemStatus = new Element();
			itemStatus.setCount(this.count);
			itemStatus.setBindStatus(this.getBindStatus());
			itemStatus.setValue(resolvedItemValue);
			itemStatus.setText(resolvedItemLabel);
			this.pageContext.setAttribute(this.getVarStatus(), itemStatus);
		}

		if (StringUtils.hasText(this.getVarValue())) {
			this.pageContext
					.setAttribute(this.getVarValue(), resolvedItemValue);
		}

		if (StringUtils.hasText(this.getVarLabel())) {
			this.pageContext
					.setAttribute(this.getVarLabel(), resolvedItemLabel);
		}

		if (!LAYOUT_CUSTOMALL.equals(this.getLayout())) {
			// 非完全自定义，由系统产生checkbox
			AbstractHtmlElementTag itemTag = this
					.writeCheckableTagContent(resolvedItemValue);

			if (!LAYOUT_CUSTOMLABEL.equals(this.getLayout())) {
				// 非自定义label，由系统产生label
				// output label
				this.tagWriter.startTag("span");
				String id = itemTag.getId();
				if (StringUtils.hasText(id)) {
					this.tagWriter.writeAttribute(ID_ATTRIBUTE, this
							.getDisplayString(this.evaluate(ID_ATTRIBUTE, id),
									this.getPropertyEditor())
							+ WebDataBinder.DEFAULT_FIELD_MARKER_PREFIX
							+ "label");
				} else {
					writeOptionalAttribute(this.tagWriter, ID_ATTRIBUTE,
							autogenerateId()
									+ WebDataBinder.DEFAULT_FIELD_MARKER_PREFIX
									+ "label");
				}
				this.tagWriter.forceBlock();
				this.tagWriter.appendValue(resolvedItemLabel);
				this.tagWriter.endTag();

				if (LAYOUT_VERTICAL.equals(this.getLayout())) {
					// 垂直布局输出<br/>
					this.tagWriter.startTag("br");
					this.tagWriter.endTag();
				} else if (LAYOUT_HORIZONTAL.equals(this.getLayout())) {
				}
			}
		}
	}

	protected abstract AbstractHtmlElementTag writeCheckableTagContent(
			Object value) throws JspException;

	private Object resolveItemValue(BeanWrapper itemValueBeanWrapper)
			throws JspException {
		// items
		Object resolvedItemValue = null;
		if (StringUtils.hasText(this.getItemValue())) {
			resolvedItemValue = itemValueBeanWrapper.getPropertyValue(this
					.getItemValue());
		} else {
			resolvedItemValue = this.getItems();
		}
		if (resolvedItemValue == null) {
			throw new IllegalArgumentException(
					"Attribute 'items' is required when binding to non-boolean values");
		}
		return resolvedItemValue;
	}

	private String resolveItmeLabel(BeanWrapper itemLabelBeanWrapper)
			throws JspException {
		String resolvedItemLabel = null;
		if (StringUtils.hasText(this.getItemLabel())) {
			resolvedItemLabel = this.getDisplayString(itemLabelBeanWrapper
					.getPropertyValue(this.getItemLabel()));
		}
		return resolvedItemLabel;
	}

	public static class Element {
		private BindStatus bindStatus;
		private String text;
		private Object value;
		private int count;

		private Element() {

		}

		/**
		 * @param bindStatus
		 *            the bindStatus to set
		 */
		protected void setBindStatus(BindStatus bindStatus) {
			this.bindStatus = bindStatus;
		}

		protected void initBindStatus(RequestContext requestContext, String path) {
			this.bindStatus = new BindStatus(requestContext, path, false);
		}

		protected BindStatus getBindStatus() {
			Assert.notNull(this.bindStatus,
					"bindStatus is null, Please initialize bindStatus first!");
			return this.bindStatus;
		}

		/**
		 * @return the items
		 */
		public Object getBoundValue() {
			return this.getBindStatus().getValue();
		}

		/**
		 * @return the path
		 */
		public String getPath() {
			return this.bindStatus.getExpression();
		}

		/**
		 * @return the checked
		 * @throws LinkageError
		 * @throws ClassNotFoundException
		 * @throws NoSuchMethodException
		 * @throws SecurityException
		 * @throws InvocationTargetException
		 * @throws IllegalAccessException
		 * @throws IllegalArgumentException
		 */
		public boolean isChecked() throws ClassNotFoundException, LinkageError,
				SecurityException, NoSuchMethodException,
				IllegalArgumentException, IllegalAccessException,
				InvocationTargetException {
			Class<?> clz = ClassUtils
					.forName("org.springframework.web.servlet.tags.form.SelectedValueComparator");
			Method m = clz.getDeclaredMethod("isSelected", new Class[] {
					BindStatus.class, Object.class });
			m.setAccessible(true);
			return (Boolean) m.invoke(null, new Object[] {
					this.getBindStatus(), this.getItems() });
		}

		/**
		 * @return the text
		 */
		public String getText() {
			return text;
		}

		/**
		 * @param text
		 *            the text to set
		 */
		public void setText(String text) {
			this.text = text;
		}

		/**
		 * @return the items
		 */
		public Object getItems() {
			return value;
		}

		/**
		 * @param items
		 *            the items to set
		 */
		public void setValue(Object value) {
			this.value = value;
		}

		/**
		 * @return the index
		 */
		public int getIndex() {
			return count - 1;
		}

		/**
		 * @return the count
		 */
		public int getCount() {
			return count;
		}

		/**
		 * @param count
		 *            the count to set
		 */
		void setCount(int count) {
			this.count = count;
		}

	}

	/**
	 * @return the itemValue
	 */
	protected String getItemValue() throws JspException {
		return this.itemLabel == null ? null : this.getDisplayString(this
				.evaluate("itemValue", this.itemValue), this
				.getPropertyEditor());
	}

	/**
	 * @param itemValue
	 *            the itemValue to set
	 */
	public void setItemValue(String valuePropertyName) {
		this.itemValue = valuePropertyName;
	}

	/**
	 * @return the textPropertyName
	 */
	protected String getItemLabel() throws JspException {
		return this.itemLabel == null ? null : this.getDisplayString(this
				.evaluate("itemLabel", this.itemLabel), this
				.getPropertyEditor());
	}

	/**
	 * @param textPropertyName
	 *            the textPropertyName to set
	 */
	public void setItemLabel(String textPropertyName) {
		this.itemLabel = textPropertyName;
	}

	/**
	 * @return the var
	 * @throws JspException
	 */
	protected String getVarStatus() throws JspException {
		return this.itemLabel == null ? null : this.getDisplayString(this
				.evaluate("statusVar", this.varStatus), this
				.getPropertyEditor());
	}

	/**
	 * @param var
	 *            the var to set
	 */
	public void setVarStatus(String statusVar) {
		this.varStatus = statusVar;
	}

	/**
	 * @return the valueVar
	 * @throws JspException
	 */
	protected String getVarValue() throws JspException {
		return this.itemLabel == null ? null : this.getDisplayString(this
				.evaluate("valueVar", this.varValue), this.getPropertyEditor());
	}

	/**
	 * @param valueVar
	 *            the valueVar to set
	 */
	public void setVarValue(String valueVar) {
		this.varValue = valueVar;
	}

	/**
	 * @return the labelVar
	 * @throws JspException
	 */
	protected String getVarLabel() throws JspException {
		return this.itemLabel == null ? null : this.getDisplayString(this
				.evaluate("labelVar", this.varLabel), this.getPropertyEditor());
	}

	/**
	 * @param labelVar
	 *            the labelVar to set
	 */
	public void setVarLabel(String labelVar) {
		this.varLabel = labelVar;
	}

	/**
	 * @return the layout
	 * @throws JspException
	 */
	protected String getLayout() throws JspException {
		String l = this.getDisplayString(this.evaluate("layout", this.layout),
				this.getPropertyEditor());
		return l == null ? null : l.toUpperCase();
	}

	/**
	 * @param layout
	 *            the layout to set
	 */
	public void setLayout(String layout) {
		this.layout = layout;
	}

	/**
	 * @return the skipLastBody
	 * @throws JspException
	 */
	protected boolean getEvalLastBody() throws JspException {
		return TagUtils.getBooleanValue("evalLastBody", this.evalLastBody,
				this.pageContext);
	}

	/**
	 * @param skipLastBody
	 *            the skipLastBody to set
	 */
	public void setEvalLastBody(String skipEvalLastBody) {
		this.evalLastBody = skipEvalLastBody;
	}
}
